Kurt Hsu's blog

The Rails developer in taiwan.


  • 首頁

  • 標籤

  • 分類

  • 歸檔

[Rails]用includes解決N+1 query

發表於 2018-09-10 更新於 2019-08-27 分類於 Rails

N+1 query幾乎是Rails一定會碰到的問題了XD
剛好買的書有提到就來玩一下, 其實先以server log到底什麼時候hit sql, 以及hit了幾次就能解決

問題發生

先假設User有3個Post, 我們很常在controller裡面寫

1
2
3
def index
@posts = Post.all
end

然後顯示這篇Post的User name會在view裡寫

1
2
3
<% @post.each do |post| %>
<%= post.user.name %>
<% end %>

這時候查看server log會發現hit了sql 4次, 這個就是N+1 query, 如果今天User有1000筆post就會hit 1001次query, 就會影響效能

1
2
3
4
Post Load (0.2ms)  SELECT "posts".* FROM "posts"
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]

解決方案by includes

簡單的解決方法先把User includes近來給Post即可

1
2
3
def index
@posts = Post.includes(:user).all
end

再來查看server log只hit 2次

1
2
Post Load (0.1ms)  SELECT "posts".* FROM "posts"
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? [["id", 1]]

觀察hit時間

其實觀察hit的時間也蠻重要的, 如果寫下列程式碼

1
2
3
4
5
6
7
def index
@posts = Post.all
binding.pry
Post.first
binding.pry
puts 'asd'
end

將會發現第一次binding.pry直接撈Post.all並不會hit sql, 而第二次binding.pry的Post.first有hit sql, 這似乎是rails lazy load做的事情, 如果撈資料還有可能串上更多orm撈資料的時候他並不會真的先衝進資料庫撈資料, 一直要到確定這筆資料要被使用了或者後面不能再串orm了就會hit sql, 所以也可以發現第二次binding.pry的Post.all還是沒有hit sql, 要一直到view開始each render這些post的時候才會去hit sql, 有這個資訊的話會更好去調sql上的效能

參考書籍
為你自己學Ruby on Rails2017.09初版

# Rails # includes # N+1 query
[Rails]利用config檔設定path
[HTML]Conditional comments指定IE版本顯示內文
  • 文章目錄
  • 本站概要

Kurt Hsu

Progress One Percent Every Day
171 文章
55 分類
163 標籤
RSS
  1. 1. 問題發生
  2. 2. 解決方案by includes
  3. 3. 觀察hit時間
© 2020 Kurt Hsu
由 Hexo 強力驅動 v3.8.0
|
主題 – NexT.Muse v7.3.0